/***********************************************************************************************************
============================================================================================================
 Bits Download Manager
 http://www.codeplex.com/BITSDownloadMgr

 Component:BITSDownloadMgr.UI
 File Name:BitsStatusForm.cs
 
 
============================================================================================================
 Copyright (C) 2004-2007 Microsoft Corporation

 This source is subject to the Microsoft Public License (Ms-PL).
 See http:www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx.
 All other rights reserved.

 THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
 OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
 LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR
 FITNESS FOR A PARTICULAR PURPOSE.
============================================================================================================
***********************************************************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using BitsDownloadMgr.Interop;
using BitsDownloadMgr.Jobs;
using BitsDownloadMgr.UI.Properties;
using System.Collections.ObjectModel;
namespace BitsDownloadMgr.UI
{
    internal partial class BitsStatusForm : Form
    {

        DisposableDictionary<Guid, CopyJob> _jobList = new DisposableDictionary<Guid, CopyJob>();
        DisposableCollection<CopyFile> _jobFiles;
        private delegate void UpdateJobDelegate(CopyJob job);
        private delegate void UpdateFileDelegate(CopyFile file);

        public BitsStatusForm()
        {
            InitializeComponent();
        }

        private void BitsStatusForm_Load(object sender, EventArgs e)
        {

            addJobCreators();

            ListJobs();

            BitsUtility.CopyManager.JobCreated += new EventHandler<JobCreatedEventArgs>(CopyManager_JobCreated);
            Settings.Default.PropertyChanged += new PropertyChangedEventHandler(Default_PropertyChanged);

        }

        void Default_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            ListJobs(); 
        }


        private void addJobCreators()
        {
            List<JobCreator> jobCreators = CreatorSettings.GetJobCreators();
            foreach (JobCreator creator in jobCreators)
            {
                ToolStripButton creatorButton = new ToolStripButton(creator.Description);
                creatorButton.DisplayStyle = ToolStripItemDisplayStyle.Text;
                creatorButton.ToolTipText = creator.HelpText;
                creatorButton.Click += new EventHandler(creatorButton_Click);

                creatorButton.Tag = creator;
                newJobDropDown.DropDownItems.Add(creatorButton);
            }
        }

        #region BITS Event Handlers
        /// <summary>
        /// Handles CopyManager.JobCreated Event.  
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void CopyManager_JobCreated(object sender, JobCreatedEventArgs e)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new UpdateJobDelegate(this.AddJob), e.NewJob);
            }
            else
            {
                AddJob(e.NewJob);
            }
        }

        /// <summary>
        /// Handles Job.JobModification Event for all jobs.  
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void job_JobModification(object sender, EventArgs e)
        {
            CopyJob job = sender as CopyJob;
            if (job != null)
            {
                UpdateJobDisplay(job);
            }
        }

        /// <summary>
        /// Handles Job.JobTransferred Event for all jobs.  
        /// Calls Complete on the transferred job.  
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void job_JobTransferred(object sender, EventArgs args)
        {
            CopyJob job = (CopyJob)sender;
            UpdateJobDisplay(job);
            jobComplete(job); 
            UpdateJobDisplay(job);
        }

        #endregion

        #region Job Display Management

        /// <summary>
        /// Refreshes the job list view to show all BITS jobs in progress.  
        /// </summary>
        private void ListJobs()
        {
            Guid _selectedJobId = Guid.Empty;

            //Clear the list view if we have items.  
            if (jobListView.Items.Count > 0)
            {
                //check if one is selected.  If so, we will want to retain the selection.  
                if (jobListView.SelectedItems.Count == 1)
                {
                    _selectedJobId = new Guid(jobListView.SelectedItems[0].Name);
                }
                jobListView.Items.Clear();
            }
            fileListView.Items.Clear();
            //release our tracking list.  
            if (_jobList.Count > 0)
            {
                _jobList.Dispose();
                _jobList = new DisposableDictionary<Guid, CopyJob>();
            }
            try
            {
                //Get the list of jobs.  
                Collection<CopyJob> jobList = BitsUtility.CopyManager.GetJobs();
                foreach (CopyJob job in jobList)
                {
                    AddJob(_selectedJobId, job);
                }
            }
            catch (Exception ex)
            {
                string message =
                    "There was an error of type " + ex.GetType().Name + " while retrieving the list of active jobs." +
                    Environment.NewLine + "Would you like to cancel all pending jobs?";
                if (MessageBox.Show(message, "Error retrieving jobs", MessageBoxButtons.YesNo) == DialogResult.Yes)
                {
                    try
                    {
                        BitsUtility.CopyManager.CancelAllJobs(); 
                    }
                    catch (Exception ex2)
                    {
                        string message2 = "Could not cancel all pending jobs." + Environment.NewLine +
                            "Please use the BITSAdmin tool to cancel these jobs." + Environment.NewLine +
                            "BITS Download Manager will now close."; 
                        MessageBox.Show(message);
                        System.Windows.Forms.Application.Exit(); 
                    }
                }
            }
        }

        /// <summary>
        /// Adds a selected job to the UI.  
        /// If the Id of the job matches the selectedJobId, 
        /// that job is selected in the ListView.  
        /// </summary>
        /// <param name="selectedJobId">JobId of the selected job.</param>
        /// <param name="job"></param>
        private void AddJob(Guid selectedJobId, CopyJob job)
        {
            try
            {
                //add the job to the list view.  
                //we will use the job object as the tag.  
                _jobList.Add(job.Id, job);
                ListViewItem newItem = AddJobListViewItem(job);
                if (job.Id == selectedJobId)
                {
                    newItem.Selected = true;
                }
            }
            catch (System.Runtime.InteropServices.COMException ex)
            {
                BitsUtility.showBitsError(ex, "Could not create the job.");
            }

            job.JobModification += new EventHandler<EventArgs>(job_JobModification);
            job.JobTransferred += new EventHandler<EventArgs>(job_JobTransferred);

        }

        /// <summary>
        /// Adds a job to the UI.  Job will never be selected.  
        /// </summary>
        /// <param name="job"></param>
        private void AddJob(CopyJob job)
        {
            AddJob(Guid.Empty, job);
        }

        /// <summary>
        /// Adds a job to the list view.  
        /// </summary>
        /// <param name="job"></param>
        /// <returns></returns>
        private ListViewItem AddJobListViewItem(CopyJob job)
        {
            ListViewItem jobItem = new ListViewItem(job.DisplayName);
            jobItem.SubItems.Add(job.Description);
            jobItem.SubItems.Add(job.State.ToString());
            jobItem.SubItems.Add(job.Priority.ToString());
            jobItem.SubItems.Add(GetBytesStatusText(job));
            jobItem.SubItems.Add(GetFilesStatusText(job));
            jobItem.Name = job.Id.ToString();
            return jobListView.Items.Add(jobItem);
        }

        /// <summary>
        /// Wrapper to update the job display.
        /// Will Invoke as necessary to ensure proper marshalling.  
        /// </summary>
        /// <param name="job"></param>
        private void UpdateJobDisplay(CopyJob job)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new UpdateJobDelegate(UpdateJobListViewItem), new object[] { job });
                this.Invoke(new UpdateJobDelegate(updateJobStatusBar), new object[] { job });
                if (SelectedCopyJob != null)
                {
                    if (job.Id == SelectedCopyJob.Id)
                    {
                        this.Invoke(new MethodInvoker(UpdateSelectedJob));
                    }
                }
            }
            else
            {
                //do the progress update for the job.  
                UpdateJobListViewItem(job);
                updateJobStatusBar(job);
                if (SelectedCopyJob != null)
                {
                    if (job.Id == SelectedCopyJob.Id)
                    {
                        this.Invoke(new MethodInvoker(UpdateSelectedJob));
                    }
                }
            }
        }

        /// <summary>
        /// Updates the list view item for a job.  
        /// Does *not* call invoke.  
        /// </summary>
        /// <param name="job"></param>
        private void UpdateJobListViewItem(CopyJob job)
        {
            //Get the list view item for the job.  
            //job.UpdateProgress();
            ListViewItem[] jobItems = jobListView.Items.Find(job.Id.ToString(), false);
            if (jobItems != null)
            {
                ListViewItem jobItem = jobItems[0];
                jobItem.SubItems[2].Text = job.State.ToString();
                jobItem.SubItems[3].Text = job.Priority.ToString();
                jobItem.SubItems[4].Text = GetBytesStatusText(job);
                jobItem.SubItems[5].Text = GetFilesStatusText(job);
                if (this.WindowState == FormWindowState.Minimized)
                {
                    this.Text = GetBytesStatusText(job);
                }
                else
                {
                    this.Text = "JarJar BITS";
                }
            }
        }

        /// <summary>
        /// Gets the files transferred status text for a job.  
        /// </summary>
        /// <param name="job"></param>
        /// <returns></returns>
        private static string GetFilesStatusText(CopyJob job)
        {
            if (job.State == BitsJobState.Transferred)
            {
#if DEBUG 
                System.Diagnostics.Debug.WriteLine(job.Progress.FilesTotal.ToString(Utility.NumberFormatProvider));
                System.Diagnostics.Debug.WriteLine(job.Progress.FilesTransferred.ToString(Utility.NumberFormatProvider));
#endif 
            }
            return job.FilesTransferred.ToString(Utility.NumberFormatProvider) + " of " + job.FilesTotal.ToString(Utility.NumberFormatProvider);
        }

        /// <summary>
        /// Gets the bytes transferred status text for a job.  
        /// </summary>
        /// <param name="job"></param>
        /// <returns></returns>
        private static string GetBytesStatusText(CopyJob job)
        {
            return FormatBytesForDisplay(job.BytesTransferred) + " of " + FormatBytesForDisplay(job.BytesTotal);
        }

        private static string GetBytesStatusText(CopyFile file)
        {
            return FormatBytesForDisplay(file.BytesTransferred) + " of " + FormatBytesForDisplay(file.BytesTotal);
        }


        #endregion

        private CopyJob _selectedCopyJob;
        public CopyJob SelectedCopyJob
        {
            get
            {
                //what's the current job? 
                return _selectedCopyJob;
            }
            set
            {
                _selectedCopyJob = value;
                UpdateSelectedJob();
                ListSelectedJobFiles();
            }
        }

        private void ListSelectedJobFiles()
        {
            if (_jobFiles != null)
            {
                _jobFiles.Dispose();
            }
            fileListView.Items.Clear();
            if (SelectedCopyJob != null)
            {
                _jobFiles = SelectedCopyJob.GetFiles();
                foreach (CopyFile currentFile in _jobFiles)
                {
                    currentFile.EnableProgressUpdates(5);
                    currentFile.FileProgress += new EventHandler(currentFile_FileProgress);
                    AddFileListViewItem(currentFile);
                }
            }

        }

        void currentFile_FileProgress(object sender, EventArgs e)
        {
            CopyFile file = (CopyFile)sender;
            if (this.InvokeRequired)
            {
                this.Invoke(new UpdateFileDelegate(this.updateFileListViewItem), file);
            }
            else
            {
                updateFileListViewItem(file);
            }
        }

        private void updateFileListViewItem(CopyFile file)
        {
            try
            {
                ListViewItem fileListViewItem = fileListView.Items.Find(file.RemoteName, false)[0];
                if (fileListViewItem != null)
                {
                    fileListViewItem.SubItems[2].Text = GetBytesStatusText(file);
                    fileListViewItem.SubItems[3].Text = file.PercentComplete.ToString("p", Utility.NumberFormatProvider);
                }
            }
            catch 
            {
                //just swallow.  
            }
        }
        private ListViewItem AddFileListViewItem(CopyFile currentFile)
        {
            ListViewItem listViewItem = new ListViewItem(currentFile.RemoteName);
            listViewItem.Name = currentFile.RemoteName;
            listViewItem.SubItems.Add(currentFile.LocalName);
            listViewItem.SubItems.Add(GetBytesStatusText(currentFile));
            listViewItem.SubItems.Add(currentFile.PercentComplete.ToString("p", 
                Utility.NumberFormatProvider));
            return fileListView.Items.Add(listViewItem);

        }

        private void UpdateSelectedJob()
        {

            CopyJob currentSelectedJob = SelectedCopyJob;
            if (currentSelectedJob != null)
            {
                //Update the buttons/menu items enabled flags.
                resumeJob.Enabled = resumeJobToolStripMenuItem.Enabled = currentSelectedJob.CanResume;
                pauseJob.Enabled = pauseJobToolStripMenuItem.Enabled = currentSelectedJob.CanPause;
                cancelJob.Enabled = cancelJobToolStripMenuItem.Enabled = currentSelectedJob.CanCancel;
                confirmJob.Enabled = confirmJobToolStripMenuItem.Enabled = currentSelectedJob.CanComplete;

                //Update the priority checks ... 
                int priorityValue = (int)currentSelectedJob.Priority;
                foreach (ToolStripMenuItem item in priorityToolStripMenuItem.DropDownItems)
                {
                    item.Checked = (int.Parse((string)item.Tag, Utility.NumberFormatProvider) == priorityValue);
                }
            }
            else
            {
                resumeJob.Enabled = resumeJobToolStripMenuItem.Enabled = false;
                pauseJob.Enabled = pauseJobToolStripMenuItem.Enabled = false;
                cancelJob.Enabled = cancelJobToolStripMenuItem.Enabled = false;
                confirmJob.Enabled = confirmJobToolStripMenuItem.Enabled = false;
            }
        }

        [Obsolete()]
        private void updateProgress()
        {
            if (this.WindowState == FormWindowState.Minimized)
            {
                return;
            }
            if (_jobList == null)
            {
                return;
            }

            foreach (CopyJob job in _jobList.Values)
            {
                System.Diagnostics.Debug.WriteLine("udpating job " + job.DisplayName);
                //job.UpdateProgress();
                if (job.State == BitsJobState.Transferred)
                {
                    //Transferred indicates that all files have been transferred
                    //Calling complete moves the state to "Acknowledged"
                    job.Complete();
                }
                if (job.State == BitsJobState.Transferring)
                {
                    updateJobStatusBar(job);

                }
                if (job.State == BitsJobState.TransientError)
                {
                    // attempt restart
                    job.Resume();
                }

            }

            //if (_jobFiles != null && SelectedCopyJob.State == BitsJobState.Transferring)
            //{

            //    foreach (CopyFile file in _jobFiles)
            //    {
            //        System.Diagnostics.Debug.WriteLine("updating file " + file.RemoteName);
            //        file.UpdateProgress();
            //    }
            //}
            //else
            //{
            //    System.Diagnostics.Debug.WriteLine("Job Files is null");
            //}


        }

        private void updateJobStatusBar(CopyJob job)
        {
            currentTransferRate.Text = "Current: " + FormatBytesForDisplay(job.LastTransferRate) + "/sec";
            avgTransferRate.Text = "Average: " + FormatBytesForDisplay(job.AverageTransferRate) + "/sec";

            int jobPercentComplete = (int)(((float)job.BytesTransferred / (float)job.BytesTotal) * 100);
            if (jobPercentComplete >= jobProgressBar.Minimum && jobPercentComplete <= jobProgressBar.Maximum)
            {
                jobProgressBar.Value = jobPercentComplete;
            }
            else
            {
                jobProgressBar.Value = jobProgressBar.Minimum;
            }

            jobProgressBar.ToolTipText = jobPercentComplete.ToString(Utility.NumberFormatProvider) + "% Complete";
        }

        #region Job Control Methods
        private void jobCancel()
        {
            CopyJob currentJob = SelectedCopyJob;
            if (currentJob != null)
            {
                currentJob.Cancel();
            }
        }

        private void jobPause()
        {
            CopyJob currentJob = SelectedCopyJob;
            if (currentJob != null)
            {
                currentJob.Suspend();
            }
        }

        private void jobResume()
        {
            CopyJob currentJob = SelectedCopyJob;
            if (currentJob != null)
            {
                if (currentJob.State == BitsJobState.Transferred)
                {
                    currentJob.Complete();
                }
                else
                {
                    currentJob.Resume();
                }
            }
        }
        private void jobComplete()
        {
            CopyJob currentJob = SelectedCopyJob;

            if (currentJob != null)
            {
                jobComplete(currentJob);
            }
        }

        private void jobComplete(CopyJob currentJob)
        {
            if (currentJob.CompleteError == null)
            {
                try
                {
                    currentJob.Complete();
                }
                catch (Exception e)
                {
                    if (Utility.ShowMessageBox("An attempt to complete the Copy Job " + currentJob.DisplayName + " failed with the following exception:\n"
                            + GetExceptionMessage(e) + "\nWould you like to cancel the job?",
                            "Job Completion Exception", MessageBoxButtons.YesNo,MessageBoxIcon.Question)
                        == DialogResult.Yes)
                    {
                        currentJob.Cancel();
                    }
                }
            }
            else
            {
                if (Utility.ShowMessageBox("The selected job has previously failed to complete do to the following exception:\n" +
                        GetExceptionMessage(currentJob.CompleteError) + "\nDo you want to attempt to force completion?",
                        "Confirm Complete", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
                        == DialogResult.Yes)
                {
                    try
                    {
                        currentJob.Complete(true);
                    }
                    catch (Exception ex)
                    {
                        Utility.ShowMessageBox("The attempt to complete the job failed with the following exception:\n" +
                            GetExceptionMessage(ex), "Complete Job Failed");
                    }
                }

            }
        }
        private static string GetExceptionMessage(Exception ex)
        {
            return ex.GetType().Name + ": " + ex.Message;
        }
        private void createJob(ICreateJob jobCreator)
        {
            try
            {
                CopyJob newCopyJob = jobCreator.CreateJob(BitsUtility.CopyManager, this);
                if (newCopyJob != null)
                {
                    newCopyJob.Resume();
                }
            }
            catch (System.Runtime.InteropServices.COMException comEx)
            {
                BitsUtility.showBitsError(comEx, "Could no create new job."); 
            }

        }
        #endregion

        #region Toolbar button event handlers
        private void doRefreshView(object sender, EventArgs e)
        {
            ListJobs();
        }

        private void doResumeJob(object sender, EventArgs e)
        {
            jobResume();
        }

        private void doJobPause(object sender, EventArgs e)
        {
            jobPause();
        }

        private void doJobCancel(object sender, EventArgs e)
        {
            jobCancel();
        }


        void creatorButton_Click(object sender, EventArgs e)
        {
            ToolStripButton clickedButton = (ToolStripButton)sender;
            JobCreator selectedCreator = (JobCreator)clickedButton.Tag;
            //create the job creator.  
            Type jobCreatorType = Type.GetType(selectedCreator.ClassName);
            ICreateJob jobCreator
                = (ICreateJob)Activator.CreateInstance(jobCreatorType);
            createJob(jobCreator);
        }
        #endregion


        private static string FormatBytesForDisplay(double byteValue)
        {
            switch (Properties.Settings.Default.BytesDisplay)
            {
                case BytesDisplayOption.Bytes:
                    return byteValue.ToString("0", Utility.NumberFormatProvider) + " B";

                case BytesDisplayOption.Kilobytes:
                    double KBValue = byteValue / 1024;
                    return KBValue.ToString("0.00", Utility.NumberFormatProvider) + " KB";

                case BytesDisplayOption.Megabytes:
                    double MBValue = byteValue / 1048576;
                    return MBValue.ToString("0.00",Utility.NumberFormatProvider) + " MB";

                default:
                    return byteValue.ToString("0",Utility.NumberFormatProvider) + " B";

            }
        }

        private void jobListView_selectedIndexChanged(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.Write("Selected Index Changed");
            if (jobListView.SelectedIndices.Count > 0)
            {
                //what is the selected index? 
                ListViewItem selectedItem = jobListView.Items[jobListView.SelectedIndices[0]];
                //get the job for the selected item.  
                SelectedCopyJob = getJobForListViewItem(selectedItem);
            }
            else
            {
                SelectedCopyJob = null;
            }

        }
        private CopyJob getJobForListViewItem(ListViewItem item)
        {
            Guid jobId = new Guid(item.Name);
            CopyJob job = _jobList[jobId];
            return job;
        }

        private void priorityItemClicked(object sender, EventArgs e)
        {
            if (SelectedCopyJob != null)
            {
                ToolStripMenuItem menuItem = (ToolStripMenuItem)sender;
                SelectedCopyJob.Priority = (BitsJobPriority)int.Parse((string)menuItem.Tag, Utility.NumberFormatProvider);

            }
        }

        private void doCompleteJob(object sender, EventArgs e)
        {
            jobComplete();
        }

        private void optionsButton_Click(object sender, EventArgs e)
        {
            SettingsForm frm = new SettingsForm(); 
            frm.ShowDialog(this); 
        }
    }
}
